﻿using Newtonsoft.Json.Linq;
using Niknak.Services.TaskScheduler.Processors;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace Niknak.Services.TaskScheduler
{
    public static class Utils
    {
        public static T CreateInstance<T>(string forType)
        {
            Type clrType = Type.GetType(forType);

            if (clrType == null)
                throw new Exception("Invalid CLR Type " + forType);

            object instance = Activator.CreateInstance(clrType);

            if (!(instance is T))
                throw new Exception("Specified CLR Type " + forType + " does not implement " + typeof(T).ToString());

            return (T)instance;
        }

        /// <summary>
        /// See http://thevalerios.net/matt/2008/05/use-threadpoolqueueuserworkitem-with-anonymous-types/
        /// </summary>
        public static bool QueueUserWorkItem<T>(T state, Action<T> callback)
        {
            return ThreadPool.QueueUserWorkItem(s => callback((T)s), state);
        }

        public static bool QueueUserWorkItem(Action callback)
        {
            return ThreadPool.QueueUserWorkItem(s => callback());
        }

        public static IEnumerable<FieldInfo> GetStartArguments(Type forProcessor)
        {
            return forProcessor.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.GetCustomAttribute<StartArgumentAttribute>() != null);
        }

        public static JObject ParseStartArgumentsJson(string startArguments)
        {
            JObject jsonArguments = null;

            try
            {
                if (startArguments.StartsWith("{"))
                    jsonArguments = JObject.Parse(startArguments);
            }
            catch { }

            return jsonArguments;
        }

        public static object GetStartArgumentsJsonValue(FieldInfo field, JObject arguments)
        {
            return GetStartArgumentsJsonValue<object>(field, arguments);
        }

        public static T GetStartArgumentsJsonValue<T>(FieldInfo field, JObject arguments)
        {
            CustomAttributeData attributeData = GetAttributeData<StartArgumentAttribute>(field);
            bool isRequired = GetAttributeValue<bool>(attributeData, "IsRequired");
            T defaultValue = GetAttributeValue<T>(attributeData, "DefaultValue");

            //try and return the saved value first
            if (arguments != null && arguments.Property(field.Name) != null)
            {
                T savedVal = (T)arguments.GetValue(field.Name).ToObject(field.FieldType);
                //if the value is present return it
                if (savedVal != null && !string.IsNullOrEmpty(savedVal.ToString()))
                    return savedVal;
                //if this argument isn't required don't worry about returning the DefaultValue for this argument
                else if (!isRequired)
                    return default(T);
            }
            
            //otherwise use the default value set in the attribute for this field, of the default for T if not set
            return defaultValue;
        }

        public static void InitStartArguments(object processorInstance, string startArguments)
        {
            InitStartArguments(processorInstance, ParseStartArgumentsJson(startArguments));
        }

        public static void InitStartArguments(object processorInstance, JObject startArguments)
        {
            if (processorInstance == null)
                return;

            //if the ProcessorInstance type contains fields with the StartArgument attribute
            //AND the CustomSettings.StartArguments is JSON...

            IEnumerable<FieldInfo> argumentFields = GetStartArguments(processorInstance.GetType());

            if (!argumentFields.Any())
                return;

            //for each field in the ProcessorInstance type marked with the StartArgument attribute,
            //look for a corresponding value in the JSON from CustomSettings.StartArguments
            
            foreach (FieldInfo field in argumentFields)
                field.SetValue(processorInstance, GetStartArgumentsJsonValue(field, startArguments));
        }

        public static CustomAttributeData GetAttributeData<T>(FieldInfo forField)
        {
            return forField.GetCustomAttributesData().SingleOrDefault(a => a.AttributeType == typeof(T));
        }

        public static T GetAttributeValue<T>(CustomAttributeData attributeData, string memberName)
        {
            CustomAttributeNamedArgument namedArg = attributeData.NamedArguments.SingleOrDefault(a => a.MemberName == memberName);
            if (namedArg == null || namedArg.MemberInfo == null)
                return default(T);

            return (T)namedArg.TypedValue.Value;
        }
    }
}
